/*
 *  ScannerFSM.cpp
 *  Parser
 *
 *  Created by Christoph Ehscheidt on 10.02.08.
 *  Copyright 2008 __MyCompanyName__. All rights reserved.
 *
 */

#include "ScannerFSM.h"

	ScannerFSM::ScannerFSM(SymtableInterface* symtable) {
		this->lex = new Lexem(25);

		//this->buffer = new ScanBufferRing(filename, bufferBlockSize);
		this->symtable = symtable;

		this->counter = 0;
				
		this->endState = false;
		this->eof = false;
	}

	ScannerFSM::~ScannerFSM() {
		delete this->buffer;
		delete this->lex;
	}

	void ScannerFSM::setFile(char* filename, unsigned int bufferBlockSize) {
		this->buffer = new ScanBufferRing(bufferBlockSize);
		this->buffer->openFile(filename);
	}
	
	/**
	* Start of ScannerFSM
	* @return The next valid token read out from the inputfile
	*/
	Token* ScannerFSM::getNextToken() {
		return this->s_start();
	}

	/**
	* Return the next "char" from buffer
	* char append into the vector
	*/
	char ScannerFSM::nextChar() {
		char c = ' ';

		try {
			// Read next char from buffer
			c = this->buffer->getNextChar();
			
		} catch(ScanBufferIOException e) {
			
			// If endstate, then set eof flag
			if (this->endState) this->eof = true;

			/*
			 * If no endstate is reached, sp char (32) will be read and the processing have to be continued until
			 * the fsm is in a final state.
			 */
		}
		
		// Append char zu lexem
		this->lex->append(c);

		return c;
	}
	
	/**
	* Rewinds the buffer and the vector with 1
	*/
	void ScannerFSM::unGetChar() {
		this->buffer->unGetChar();
		this->lex->rewind(1);
	}

	/**
	* The scanner logic
	*/
	Token* ScannerFSM::s_start(){
		this->lex->reset();
		// Startstate is a endstate, too
		this->endState = true;
		
		// Read next char
		char c = this->nextChar();
		
		// If EOF is reached return null pointer because no further tokens are remaining
		if(this->eof)
		{
			return NULL;
		}
		
		// A-Z, a-z
		if( isCharText(c) ) {
			return this->s_identifier();
		}
		
		// 0 - 9
		if( isCharNumeric(c) ) {
			return this->s_int();
		}
		
		switch(c) {
			case '+':
				return this->s_plus();
			case '-':
				return this->s_minus();
			case '*':
				return this->s_multi();
			case '/':
				return this->s_div();
			case ')':
				return this->s_braket_close();
			case '(':
				return this->s_braket_open();
			case ':':
				return this->s_colon();
			case ',':
				return this->s_comma();
			case ';':
				return this->s_semicolon();
			case ' ':
				return this->s_start();
			case 9: // TAB
				return this->s_start();
			case 10: // LF
				return this->s_start();
			case 13: // CR
				return this->s_start();
						
			}

		this->throwSyntaxException();
		return 0;
	}

	/**
	* Token identifier
	*/
	Token* ScannerFSM::s_identifier() {
		this->endState = true;
		
		char c = this->nextChar();
		
		// A-Z, a-z, 0-9
		if( isCharNumeric(c) || isCharText(c) ) {
			return this->s_identifier();
		} 

		// One step back because the current char does not fit to the token identifier
		this->unGetChar();			
			
		// insert into symtable
		Element* e = this->insertIntoSymtable(TokenTyp::TOKEN_IDENT);
		int t = e->getTokenTyp();

		int column = this->buffer->getColumn() - this->lex->getLength();
		
		// Is it a user definied identifier or a reserved one?
		if(t == TokenTyp::TOKEN_IDENT) {
			return new TokenIdent(this->buffer->getLine(), column, e->getLexem(), this->lex->getLength() );
		} else if(t == TokenTyp::TOKEN_PROD) {
			return new TokenProduct(this->buffer->getLine(), column);
		} else if(t == TokenTyp::TOKEN_SUM) {
			return new TokenSum(this->buffer->getLine(), column);
		} else if(t == TokenTyp::TOKEN_PRINT) {
			return new TokenPrint(this->buffer->getLine(), column);
		} else if(t == TokenTyp::TOKEN_MODULO) {
			return new TokenModulo(this->buffer->getLine(), column);
		} else if(t == TokenTyp::TOKEN_DURCH) {
			return new TokenDurch(this->buffer->getLine(), column);
		}

		// shouldn't be reached
		return 0;
	}
	
	/**
	* Token plus
	*/
	Token* ScannerFSM::s_plus() {
		return new Token(this->buffer->getLine(), this->buffer->getColumn()-1, (char*) "Plus", TokenTyp::TOKEN_PLUS);
	}
	
	/**
	* Token minus
	*/
	Token* ScannerFSM::s_minus() {
		return new Token(this->buffer->getLine(), this->buffer->getColumn()-1, (char*) "Minus", TokenTyp::TOKEN_MINUS);
	}
	
	/**
	* Token multi
	*/
	Token* ScannerFSM::s_multi() {
		return new Token(this->buffer->getLine(), this->buffer->getColumn()-1, (char*) "Multiplication", TokenTyp::TOKEN_MULTIPLICATION);
	}
	
	/**
	* Token div
	*/
	Token* ScannerFSM::s_div() {
		return new Token(this->buffer->getLine(), this->buffer->getColumn()-1, (char*) "Division", TokenTyp::TOKEN_DIVISION);
	}
	
	/**
	* Token int
	*/
	Token* ScannerFSM::s_int() {
		this->endState = true;
		
		char c = this->nextChar();
		
		if( isCharNumeric(c) ) {
			return this->s_int();
		} else if( c == '.' ) {
			// Next token could be a real
			Token* token = this->s_real_0();
			// If token is a real, return it, else syntax error
			if (token != NULL) return token;
			// One step back (current read symbol)
			this->unGetChar();
		}
		
		// One step back because the current char does not fit to the token integer		
		this->unGetChar();
		
		// Make long from string
		long l = strtol( this->lex->getLexem(), 0, 10 );

		int column = this->buffer->getColumn() - this->lex->getLength();

		// if the indicated value as string doesn't fit in the long format of c++ 
		if(errno == ERANGE) {
			errno = 0;
			throw ScannerFSMRangeException((char*) "Range Over-/Underflow: ", this->lex->getLexem(), this->buffer->getLine(), column);
		}

		return new TokenInteger(this->buffer->getLine(), column, l );
	}
	
	/**
	* Token real state 0
	*/
	Token* ScannerFSM::s_real_0() {
		this->endState = false;
		
		char c = this->nextChar();
		
		// 0 - 9
		if( this->isCharNumeric(c) )
			return this->s_real_1();
		else
			return NULL;

		// Syntax error! After the dot there have to follow a numeric value. Word isn't element of the language!
		this->throwSyntaxException();
		return 0;
	}
	
	/**
	* Token real state 1
	*/
	Token* ScannerFSM::s_real_1() {
		this->endState = true;
		
		char c = this->nextChar();
		
		// 0 - 9
		if( isCharNumeric(c) ) {
			return this->s_real_1();
		}
		// Number in scientific presentation
		else if( c == 'E' ) {
			return this->s_real_2();
		}

		// Else next token is a normal real
		this->unGetChar();
		
		double d = strtod( this->lex->getLexem(), 0);
		
		int column = this->buffer->getColumn() - this->lex->getLength();
		
		if(errno == ERANGE) {
			errno = 0;
			throw ScannerFSMRangeException((char*) "Range Over-/Underflow: ", this->lex->getLexem(), this->buffer->getLine(), column);
		}

		return new TokenReal(this->buffer->getLine(), column, d);		
		
	}
	
	/**
	* Token real state 2
	*/
	Token* ScannerFSM::s_real_2() {
		this->endState = false;
		
		char c = this->nextChar();
		
		// 0 - 9 (unsigned exponent)
		if( isCharNumeric(c) ) {
			return this->s_real_4();
		}
		// Signed exponent
		else if( c == '+' || c == '-') {
			return this->s_real_3();
		}

		// Else next token is a normal real
		this->unGetChar();
		this->unGetChar();
		
		double d = strtod( this->lex->getLexem(), 0);
		
		int column = this->buffer->getColumn() - this->lex->getLength();

		if(errno == ERANGE) {
			errno = 0;
			throw ScannerFSMRangeException((char*) "Range Over-/Underflow: ", this->lex->getLexem(), this->buffer->getLine(), column);
		}

		return new TokenReal(this->buffer->getLine(), column, d);
	}
	
	/**
	* Token real state 3
	*/
	Token* ScannerFSM::s_real_3() {
		this->endState = false;
		
		char c = this->nextChar();
		
		// Exponent expected
		if( isCharNumeric(c) ) {
			return this->s_real_4();
		}	
		
		// Else token is a normal real
		this->unGetChar();
		this->unGetChar();
		this->unGetChar();
		
		double d = strtod( this->lex->getLexem(), 0);
		
		int column = this->buffer->getColumn() - this->lex->getLength();
		
		if(errno == ERANGE) {
			errno = 0;
			throw ScannerFSMRangeException((char*) "Range Over-/Underflow: ", this->lex->getLexem(), this->buffer->getLine(), column);
		}

		return new TokenReal(this->buffer->getLine(), column, d);

	}
	
	/**
	* Token real state 4
	*/
	Token* ScannerFSM::s_real_4() {
		this->endState = true;
		
		char c = this->nextChar();
		
		// 0 - 9 (more digits for exponent)
		if( isCharNumeric(c) ) {
			return this->s_real_4();
		}
		
		// Else number in scientific presentation identified
		this->unGetChar();

		double d = strtod( this->lex->getLexem(), 0 );

		int column = this->buffer->getColumn() - this->lex->getLength();
			
		if(errno == ERANGE) {
			errno = 0;
			throw ScannerFSMRangeException((char*) "Range Over-/Underflow: ", this->lex->getLexem(), this->buffer->getLine(), column);
		}

		return new TokenReal(this->buffer->getLine(), column, d );
	}
	
	/**
	* Token braket close
	*/
	Token* ScannerFSM::s_braket_close() {
		return new Token(this->buffer->getLine(), this->buffer->getColumn()-1, (char*) "Braket_close", TokenTyp::TOKEN_CLOSE_BRACKET);
	}
	
	/**
	* Token braket open
	*/
	Token* ScannerFSM::s_braket_open() {
		this->endState = true;
		char c = this->nextChar();
	
		// if the next char is a "*" the scanner will read a comment
		if(c == '*') {
			errno = 0;
			return this->s_comment_0();
		}
		
		// Else token "Open braket" identified
		this->unGetChar();
		return new Token(this->buffer->getLine(), this->buffer->getColumn()-1, (char*) "Braket_open", TokenTyp::TOKEN_OPEN_BRACKET);
	}
	
	/**
	* Token comment state 0
	*/
	Token* ScannerFSM::s_comment_0() {
		this->endState = true;
		char c = this->nextChar();

		// EOF inside of a comment -> scanner terminates
		if (this->eof) return NULL;
	
		// if next char isn't a "*" the scanner is still reading a comment
		if(c != '*') {
			return this->s_comment_0();
		}
		// Else it could be the end of a comment
		else {
			return this->s_comment_1();
		}
	}
	
	/**
	* Token comment state 1
	*/
	Token* ScannerFSM::s_comment_1() {
		this->endState = false;
		
		char c = this->nextChar();
	
		// if the following char isn't a "*" or a ")" the scanner is still reading a comment
		if(c != '*' && c != ')') {
			return this->s_comment_0();
		}
		// scanner is still reading a comment
		else if(c == '*') {
			return this->s_comment_1();
		}
		// Else the current char is a closed braket. The comment is now closed
		else {
			// comment dedected
			return this->s_start();
		}
	}
	
	
	/**
	* Token alloc
	*/
	Token* ScannerFSM::s_alloc() {
		this->endState = true;
		return new Token(this->buffer->getLine(), this->buffer->getColumn()-2, (char*) "Allocation", TokenTyp::TOKEN_ALLOCATION);
	}
	
	/**
	* Token colon
	*/
	Token* ScannerFSM::s_colon() {
		this->endState = false;
		
		// Equal sign expected
		if(this->nextChar() == '=') {
			return this->s_alloc();
		}

		// Else the word isn't element of the language
		this->throwSyntaxException();
		return 0;
	}

	/**
	* Token semicolon
	*/
	Token* ScannerFSM::s_semicolon() {
		this->endState = true;
		return new Token(this->buffer->getLine(), this->buffer->getColumn()-1, (char*) "Semicolon", TokenTyp::TOKEN_SEMICOLON);
	}
	
	Token* ScannerFSM::s_comma() {
		this->endState = true;
		return new Token(this->buffer->getLine(), this->buffer->getColumn()-1, (char*) "Comma", TokenTyp::TOKEN_KOMMA);
	}
	
	/**
	* Exception definition
	*/
	void ScannerFSM::throwSyntaxException() {
		throw ScannerFSMSyntaxException((char*) "Unknown Symbol:", this->lex->getLexem(), this->buffer->getLine(), this->buffer->getColumn() - this->lex->getLength());
	}
	
	/**
	* Insert into Symtabel definition
	* @param type The number of enum (0 to 3)
	* symtabel->lookup !=  NULL ==> symtable->insert
	*/
	Element* ScannerFSM::insertIntoSymtable(int type) {
		Element* e;
		if(! (e = this->symtable->lookup(this->lex->getLexem(), this->lex->getLength()) ) )
			e = this->symtable->insert(this->lex->getLexem(), this->lex->getLength(), type);
			
		return e;
	}

	/**
	* Return false or true
	* true -> from "0..9"
	* false -> ! "0..9"
	*/
	bool ScannerFSM::isCharNumeric(char c) {
		// 0 - 9
		if( (c >= '0' && c <= '9') ) {
			return true;
		}
		
		return false;
	}
	
	/**
	* Return false or true
	* true -> from "A..Z" or "a..z"
	* false -> ! "A..Z" or "a..z" 
	*/
	bool ScannerFSM::isCharText(char c) {
		// A-Z, a-z
		if( (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ) {
			return true;
		}
		
		return false;
	}
	
	/**
	* Return false or true
	* true -> "+,-,*,/"
	* false -> ! "+,-,*,/"
	*/
	bool ScannerFSM::isCharOperation(char c) {
		if( c == '+'  || c == '-' || c == '*' || c == '/' )
			return true;
			
		return false;
	}
	
	/**
	* Return false or true
	* true -> "(,),;, ,tab,\n"
	* false -> ! "(,),;, ,tab,\n,"
	*/
	bool ScannerFSM::isCharDevider(char c) {	//				TAB	   LF         CR
		if( c == '(' || c == ')' || c == ';' || c == ' ' || c == 9 || c == 10 || c == 13) {
			return true;
		}

		return false;
	}
